package net.wicp.tams.common.metrics.core;

import com.codahale.metrics.*;
import com.codahale.metrics.Timer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import net.wicp.tams.common.metrics.entity.statisticbean.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;


public class JsonReporter extends ScheduledReporter{

    public static Builder forRegistry(MetricRegistry registry) {
        return new Builder(registry);
    }

    public enum JsonLoggingLevel {TRACE, DEBUG, INFO, WARN, ERROR}
    private StatisticCounterBean scb = new StatisticCounterBean();
    private StatisticTimerBean stb = new StatisticTimerBean();
    private StatisticGaugeBean sgb = new StatisticGaugeBean();
    private StatisticMeterBean smb = new StatisticMeterBean();
    private StatisticHistogramBean shb = new StatisticHistogramBean();
    private List<StatisticGroupBean> sgbs = new ArrayList<StatisticGroupBean>();
    private static final ObjectMapper mapper = new ObjectMapper().registerModule(
            new TsMetricsSerializer(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, false, MetricFilter.ALL)
    );
    public static class Builder {
        private final MetricRegistry registry;
        private TimeUnit rateUnit;
        private TimeUnit durationUnit;
        private MetricFilter filter;

        private Logger logger;
        private JsonLoggingLevel loggingLevel;
        private Marker marker;



        private Builder(MetricRegistry registry){
            this.registry = registry;
            this.logger = LoggerFactory.getLogger("mectrics");
            this.marker = null;
            this.rateUnit = TimeUnit.SECONDS;
            this.durationUnit = TimeUnit.MILLISECONDS;
            this.filter = MetricFilter.ALL;
            this.loggingLevel = JsonLoggingLevel.INFO;
        }

        /**
         * Log metrics to the given logger.
         *
         * @param logger an SLF4J {@link Logger}
         * @return {@code this}
         */
        public Builder outputTo(Logger logger) {
            this.logger = logger;
            return this;
        }

        /**
         * Mark all logged metrics with the given marker.
         *
         * @param marker an SLF4J {@link Marker}
         * @return {@code this}
         */
        public Builder markWith(Marker marker) {
            this.marker = marker;
            return this;
        }

        /**
         * Convert rates to the given time unit.
         *
         * @param rateUnit a unit of time
         * @return {@code this}
         */
        public Builder convertRatesTo(TimeUnit rateUnit) {
            this.rateUnit = rateUnit;
            return this;
        }

        /**
         * Convert durations to the given time unit.
         *
         * @param durationUnit a unit of time
         * @return {@code this}
         */
        public Builder convertDurationsTo(TimeUnit durationUnit) {
            this.durationUnit = durationUnit;
            return this;
        }

        /**
         * Only report metrics which match the given filter.
         *
         * @param filter a {@link MetricFilter}
         * @return {@code this}
         */
        public Builder filter(MetricFilter filter) {
            this.filter = filter;
            return this;
        }

        /**
         * Use Logging Level when reporting.
         *
         * @param loggingLevel a (@link Slf4jReporter.LoggingLevel}
         * @return {@code this}
         */
        public Builder withLoggingLevel(JsonLoggingLevel loggingLevel) {
            this.loggingLevel = loggingLevel;
            return this;
        }

        /**
         * Builds a {@link Slf4jReporter} with the given properties.
         *
         * @return a {@link Slf4jReporter}
         */
        public JsonReporter build() {
            JsonLoggerProxy loggerProxy;
            switch (loggingLevel) {
                case TRACE:
                    loggerProxy = new TraceJsonLoggerProxy(logger);
                    break;
                case INFO:
                    loggerProxy = new InfoJsonLoggerProxy(logger);
                    break;
                case WARN:
                    loggerProxy = new WarnJsonLoggerProxy(logger);
                    break;
                case ERROR:
                    loggerProxy = new ErrorJsonLoggerProxy(logger);
                    break;
                default:
                case DEBUG:
                    loggerProxy = new DebugJsonLoggerProxy(logger);
                    break;
            }
            return new JsonReporter(registry, loggerProxy, marker, rateUnit, durationUnit, filter);
        }
    }

    private final JsonLoggerProxy loggerProxy;
    private final Marker marker;

    private JsonReporter(MetricRegistry registry,
                         JsonLoggerProxy loggerProxy,
                          Marker marker,
                          TimeUnit rateUnit,
                          TimeUnit durationUnit,
                          MetricFilter filter) {
        super(registry, "logger-reporter", filter, rateUnit, durationUnit);
        this.loggerProxy = loggerProxy;
        this.marker = marker;
    }

    @Override
    public void report(SortedMap<String, Gauge> gauges,
                       SortedMap<String, Counter> counters,
                       SortedMap<String, Histogram> histograms,
                       SortedMap<String, Meter> meters,
                       SortedMap<String, Timer> timers) {
//        for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
//            logJsonGauge(entry.getKey(), entry.getValue());
//        }
//
//        for (Map.Entry<String, Counter> entry : counters.entrySet()) {
//            logJsonCounter(entry.getKey(), entry.getValue());
//        }
//
//        for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
//            logJsonHistogram(entry.getKey(), entry.getValue());
//        }
//
//        for (Map.Entry<String, Meter> entry : meters.entrySet()) {
//            logJsonMeter(entry.getKey(), entry.getValue());
//        }
//
//        for (Map.Entry<String, Timer> entry : timers.entrySet()) {
//            logJsonTimer(entry.getKey(), entry.getValue());
//        }
        for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
            StatisticGaugeBean sgb = new StatisticGaugeBean();
            sgb.setName(entry.getKey());
            sgb.setGauge(entry.getValue());
            group(sgb);
        }

        for (Map.Entry<String, Counter> entry : counters.entrySet()) {
            StatisticCounterBean scb = new StatisticCounterBean();
            scb.setName(entry.getKey());
            scb.setCounter(entry.getValue());
            group(scb);
        }

        for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
            StatisticHistogramBean shb = new StatisticHistogramBean();
            shb.setName(entry.getKey());
            shb.setHistogram(entry.getValue());
            group(shb);
        }

        for (Map.Entry<String, Meter> entry : meters.entrySet()) {
            StatisticMeterBean smb = new StatisticMeterBean();
            smb.setName(entry.getKey());
            smb.setMeter(entry.getValue());
            group(smb);
        }

        for (Map.Entry<String, Timer> entry : timers.entrySet()) {
            StatisticTimerBean stb = new StatisticTimerBean();
            stb.setName(entry.getKey());
            stb.setTimer(entry.getValue());
            group(stb);
        }
        logJsonGroup();
    }

    private void group(StatisticBean sb){
        //1. parse map to group
        //2. group output
        for (StatisticGroupBean sgb: sgbs) {
            if(sb.getName().contains(sgb.getGroupName())){
                sgb.getMetricSet().add(sb);
                return;
            }
        }
        String[] strs = sb.getName().split("\\.");
        if(strs.length < 6)
            return;
        String groupName = strs[4] + "." + strs[5];
        StatisticGroupBean newSgb = new StatisticGroupBean(groupName);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        newSgb.setTimestamp(df.format(new Date()));
        newSgb.getMetricSet().add(sb);
        sgbs.add(newSgb);
    }

    private void logJsonGroup(){
        try {
            for(StatisticGroupBean sgb : sgbs){
                loggerProxy.log(marker, "{}", mapper.writeValueAsString(sgb));
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        for(StatisticGroupBean sgb: sgbs){
            sgb.getMetricSet().clear();
        }
        sgbs.clear();
    }

    private void logJsonTimer(String name, Timer timer) {
        try {
            stb.setName(name);
            stb.setTimer(timer);
            loggerProxy.log(marker, "{}", mapper.writeValueAsString(stb));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    private void logJsonMeter(String name, Meter meter) {
        try {
            smb.setName(name);
            smb.setMeter(meter);
            loggerProxy.log(marker, "{}", mapper.writeValueAsString(smb));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    private void logJsonHistogram(String name, Histogram histogram) {
        try {
            shb.setName(name);
            shb.setHistogram(histogram);
            loggerProxy.log(marker, "{}", mapper.writeValueAsString(shb));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    private void logJsonCounter(String name, Counter counter) {
        scb.setName(name);
        scb.setCounter(counter);
        try {
            loggerProxy.log(marker, "{}", mapper.writeValueAsString(scb));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    private void logJsonGauge(String name, Gauge gauge) {
        try {
            sgb.setName(name);
            sgb.setGauge(gauge);
            loggerProxy.log(marker, "{}", mapper.writeValueAsString(sgb));

        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected String getRateUnit() {
        return "events/" + super.getRateUnit();
    }

    /* private class to allow logger configuration */
    static abstract class JsonLoggerProxy {
        protected final Logger logger;

        public JsonLoggerProxy(Logger logger) {
            this.logger = logger;
        }

        abstract void log(Marker marker, String format, Object... arguments);
    }

    /* private class to allow logger configuration */
    private static class DebugJsonLoggerProxy extends JsonLoggerProxy {
        public DebugJsonLoggerProxy(Logger logger) {
            super(logger);
        }

        @Override
        public void log(Marker marker, String format, Object... arguments) {
            logger.debug(marker, format, arguments);
        }
    }

    /* private class to allow logger configuration */
    private static class TraceJsonLoggerProxy extends JsonLoggerProxy {
        public TraceJsonLoggerProxy(Logger logger) {
            super(logger);
        }

        @Override
        public void log(Marker marker, String format, Object... arguments) {
            logger.trace(marker, format, arguments);
        }

    }

    /* private class to allow logger configuration */
    private static class InfoJsonLoggerProxy extends JsonLoggerProxy {
        public InfoJsonLoggerProxy(Logger logger) {
            super(logger);
        }

        @Override
        public void log(Marker marker, String format, Object... arguments) {
            logger.info(marker, format, arguments);
        }
    }

    /* private class to allow logger configuration */
    private static class WarnJsonLoggerProxy extends JsonLoggerProxy {
        public WarnJsonLoggerProxy(Logger logger) {
            super(logger);
        }

        @Override
        public void log(Marker marker, String format, Object... arguments) {
            logger.warn(marker, format, arguments);
        }
    }

    /* private class to allow logger configuration */
    private static class ErrorJsonLoggerProxy extends JsonLoggerProxy {
        public ErrorJsonLoggerProxy(Logger logger) {
            super(logger);
        }

        @Override
        public void log(Marker marker, String format, Object... arguments) {
            logger.error(marker, format, arguments);
        }
    }
}
